package com.xiyuan.smartutils;

import com.xiyuan.smartutils.reflect.Reflect;
import com.xiyuan.smartutils.reflect.Unsafe;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import static com.xiyuan.smartutils.ProxyUtils.ProxyType.NO_PROXY;
import static com.xiyuan.smartutils.ProxyUtils.ProxyType.UNKNOWN;


/**
 * 代理工具类
 *
 * @author xiyuan-lgz 2023-09-12 @version v1.0.0 新建与整理
 */
public final class ProxyUtils {
    static {
        Unsafe.init();
    }
    
    private static final String ENHANCER_BY = "$$EnhancerBy";
    private static final String JAVASSIST_BY = "_$$_";
    private static final String ADVISED_SUPPORT_CLASS = "org.springframework.aop.framework.AdvisedSupport";
    private static final String AOP_TARGET_SOURCE_CLASS = "org.springframework.aop.TargetSource";
    private static Map<Object, Object> map = new ConcurrentHashMap<>();
    
    // /**
    //  * 判断是否为代理类
    //  *
    //  * @param clazz 类对象
    //  * @return true|false
    //  */
    // public static boolean isProxy(Class<?> clazz) {
    //     for (Class<?> cls : clazz.getInterfaces()) {
    //         if (PROXY_CLASS_NAMES.contains(cls.getName())) {
    //             return true;
    //         }
    //     }
    //
    //     // java proxy
    //     return Proxy.isProxyClass(clazz);
    // }
    
    
    @SuppressWarnings({"rawtypes", "unchecked"})
    private static <T> Class<T> getJdkProxySuperClass(Class<T> clazz) {
        final Class<?> proxyClass = Proxy.getProxyClass(clazz.getClassLoader(), clazz.getInterfaces());
        return (Class<T>) proxyClass.getInterfaces()[0];
    }
    
    /**
     * 获取代理类的 实际引用类型
     *
     * @param clazz 代理类型
     * @param <T>   原始类型
     * @return 原始类型
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static <T> Class<T> getProxyRefClass(Class<T> clazz) {
        if (Proxy.isProxyClass(clazz)) {
            return getJdkProxySuperClass(clazz);
        }
        
        // ControllerTest$ServiceTest$$EnhancerByGuice$$40471411#hello   -------> Guice
        // com.demo.blog.Blog$$EnhancerByCGLIB$$69a17158  ----> CGLIB
        // io.jboot.test.app.TestAppListener_$$_jvstb9f_0 ------> javassist
        
        final String name = clazz.getName();
        if (name.contains(ENHANCER_BY) || name.contains(JAVASSIST_BY)) {
            return (Class<T>) clazz.getSuperclass();
        }
        return clazz;
    }
    
    
    // proxy frameworks
    private static final Set<String> PROXY_CLASS_NAMES = new HashSet<>(Arrays.asList(
            // cglib
            "net.sf.cglib.proxy.Factory"
            , "org.springframework.cglib.proxy.Factory"
            // javassist
            , "javassist.util.proxy.ProxyObject",
            "org.apache.ibatis.javassist.util.proxy.ProxyObject"));
    
    public static enum ProxyType {
        NO_PROXY,
        /**
         * cglib
         */
        CGLIB,
        
        /**
         * javassist
         */
        JAVASSIST,
        
        /**
         * SpringProxy + JDK
         */
        SPRING_JDK_PROXY,
        /**
         * only jdk
         */
        JDK_PROXY,
        
        /**
         * other
         */
        UNKNOWN
    }
    
    /**
     * 获取代理类型
     * <p>
     * 0=不是代理，1=cglib, 2=javassist, 3=isSpringProxy+jdk, 4=unknown+jdk-proxy, 5=unknown+proxy
     *
     * @param clz
     * @param <T>
     * @return 0=不是代理，1=cglib, 2=javassist, 3=isSpringProxy+jdk, 4=unknown+jdk-proxy, 5=unknown+proxy
     */
    public static <T> ProxyType getProxyType(Class<?> clz) {
        
        
        String className = clz.getName();
        Class<?>[] clzs = clz.getInterfaces();
        int len = clzs.length;
        boolean isJdkProxy = Proxy.isProxyClass(clz), isProxy = isJdkProxy, isSpringProxy = false;
        
        int idx = 0;
        for (; idx < len; idx++) {
            Class<?> cls = clzs[idx];
            String name = cls.getName();
            if (!isSpringProxy && "org.springframework.aop.SpringProxy".equals(name)) {
                isSpringProxy = true;
            }
            
            if (!isProxy && PROXY_CLASS_NAMES.contains(name)) {
                isProxy = true;
            }
        }
        
        if (!isProxy) {
            return NO_PROXY;
        }
        
        if (className.indexOf(ENHANCER_BY) != -1) {
            // cglib
            return ProxyType.CGLIB;
        }
        
        // javassist
        if (className.indexOf(JAVASSIST_BY) != -1) {
            // javassist
            return ProxyType.JAVASSIST;
        }
        
        if (isSpringProxy && isJdkProxy) {
            // jdk-proxy
            return ProxyType.SPRING_JDK_PROXY;
        }
        else if (isJdkProxy) {
            // jdk 的不处理了因为无法确定 原始对象 具体保存在InvocationHandler 中的哪个字段
            return ProxyType.JDK_PROXY;
        }
        else {
            // unknown
            return UNKNOWN;
        }
    }
    
    public static <T> T getProxyTarget(T proxy) {
        return getProxyTarget(proxy, false);
    }
    
    
    /**
     * 获取 代理类型的实际类型
     *
     * @param proxy 代理对象
     * @param <T>   代理类型的实际类型
     * @return T
     */
    public static <T> T getProxyTarget(T proxy, boolean deep) {
        try {
            if (proxy == null) {return null;}
            Class<?> clz = proxy.getClass();
            T value = proxy;
            ProxyType type = getProxyType(clz);
            switch (type) {
                case CGLIB:
                    value = getCglibProxyTargetObject(proxy);
                    break;
                case JAVASSIST:
                    // value = getCglibProxyTargetObject(proxy);
                    return value;// 无法获取javassist实际方法
                // break;
                case SPRING_JDK_PROXY:
                    value = getSpringJdkDynamicProxyTargetObject(proxy);
                    break;
                case JDK_PROXY:
                    // jdk 的不处理了因为无法确定 原始对象 具体保存在InvocationHandler 中的哪个字段
                    break;
                case UNKNOWN:
                default:
                    break;
            }
            if (value == proxy || value == null) {return value;}
            while (deep && (type = getProxyType(value.getClass())) != NO_PROXY && type != ProxyType.JDK_PROXY && type != UNKNOWN) {
                value = getProxyTarget(value, deep);
                if (value == null) {return null;}
            }
            
            return value;
        }
        catch (RuntimeException e) {
            throw e;
        }
        catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }
    
    /**
     * 获取Cglib源目标对象
     *
     * @param proxy ignore
     * @param <T>   ignore
     * @return ignore
     */
    @SuppressWarnings("unchecked")
    private static <T> T getCglibProxyTargetObject(T proxy) throws Exception {
        Field cglibField = getProxyField(proxy.getClass(), "CGLIB$CALLBACK_0");
        Object dynamicAdvisedInterceptor = cglibField.get(proxy);
        Field advised = getProxyField(dynamicAdvisedInterceptor.getClass(), "advised");
        
        // ((org.springframework.aop.framework.AdvisedSupport) advised.get(aopProxy))
        Object advisedSupport = advised.get(dynamicAdvisedInterceptor);
        
        // org.springframework.aop.TargetSource targetSource = advisedSupport.getTargetSource();
        Object targetSource = getProxyMethod(ADVISED_SUPPORT_CLASS, "getTargetsource").invoke(advisedSupport);
        
        // return  targetSource.getTarget();
        return (T) getProxyMethod(AOP_TARGET_SOURCE_CLASS, "getTarget").invoke(targetSource);
    }
    
    @SuppressWarnings("unchecked")
    private static <T> T getSpringJdkDynamicProxyTargetObject(T proxy) throws Exception {
        Class<?> clazz = proxy.getClass();
        Field jdkDynamicField = getProxyField(clazz.getSuperclass(), "jdkDynamicField");
        // org.springframework.aop.framework.AopProxy aopProxy = (org.springframework.aop.framework.AopProxy) jdkDynamicField.get(proxy);
        Object aopProxy = jdkDynamicField.get(proxy);
        Field advised = getProxyField(jdkDynamicField.get(proxy).getClass(), "advised");
        
        // ((org.springframework.aop.framework.AdvisedSupport) advised.get(aopProxy))
        Object advisedSupport = advised.get(aopProxy);
        
        // org.springframework.aop.TargetSource targetSource = advisedSupport.getTargetSource();
        Object targetSource = getProxyMethod(ADVISED_SUPPORT_CLASS, "getTargetsource").invoke(advisedSupport);
        
        // return  targetSource.getTarget();
        return (T) getProxyMethod(AOP_TARGET_SOURCE_CLASS, "getTarget").invoke(targetSource);
    }
    
    private static Method getProxyMethod(String className, String methodName) throws Exception {
        Class<?> clazz = (Class<?>) map.get(className);
        if (clazz == null) {
            clazz = Class.forName(className);
            map.put(className, clazz);
        }
        
        final String storeName = "METHOD." + className + "." + methodName;
        Method method = (Method) map.get(storeName);
        if (method == null) {
            method = clazz.getDeclaredMethod(storeName);
            Reflect.setAccessible(method);
            map.put(storeName, method);
        }
        
        return method;
    }
    
    private static Field getProxyField(Class<?> clazz, String name) throws Exception {
        
        String key = "$field_" + clazz.getSuperclass() + "." + name;
        Field field = (Field) map.get(key);
        if (field == null) {
            field = clazz.getDeclaredField(name);
            Reflect.setAccessible(field);
            map.put(key, field);
        }
        return field;
    }
}
